import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import {
  Vector3,
  TextureLoader,
  Group,
  Clock,
  ShaderMaterial,
  Mesh,
  Curve,
  DoubleSide,
  AdditiveBlending,
  CylinderGeometry,
  Matrix4,
  MultiplyBlending,
  Vector2,
  PerspectiveCamera,
  WebGLRenderer,
  Scene,
  MeshStandardMaterial,
  BoxGeometry,
  DirectionalLight,
  BufferGeometry,
  Line,
  LineBasicMaterial,
  MeshPhongMaterial,
  MeshPhysicalMaterial,
  PlaneGeometry,
  SphereGeometry,
  SplineCurve,
  CameraHelper,
  AxesHelper,
  TubeGeometry,
  Shape,
  ExtrudeGeometry,
  EllipseCurve,
  InstancedMesh,
  CatmullRomCurve3,
  MOUSE,
  TOUCH,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import GUI from "three/examples/jsm/libs/lil-gui.module.min.js";
import {
  getInstancesMatrixAlongCurve,
  instanceAlongCurve,
  exportGLB,
} from "../utils/geometries";
import { CurveLine } from "../utils/curves.js";
import { canvasToImage } from "../utils/image";
/**
 * 现在还有一个问题 D键转向的时候，总觉得明显在打滑。
 */
const gltfLoader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
gltfLoader.setDRACOLoader(dracoLoader);

/**
 * 凸形单元
 * @param width 宽度
 * @param height 长度
 * @param offset 凸起距离
 * @param thickness 厚度
 * @param factor 凸起占比
 *
 */
function generatePart(
  width: number,
  height: number,
  offset: number,
  thickness: number,
  factor: number
) {
  const shape = new Shape();

  shape.moveTo(-width, height);
  shape.lineTo(-width * factor, height);
  shape.lineTo(-width * factor, height + offset);
  shape.lineTo(width * factor, height + offset);
  shape.lineTo(width * factor, height);
  shape.lineTo(width, height);

  shape.lineTo(width, -height);
  shape.lineTo(width * factor, -height);
  shape.lineTo(width * factor, -height + offset);
  shape.lineTo(-width * factor, -height + offset);
  shape.lineTo(-width * factor, -height);
  shape.lineTo(-width, -height);

  shape.closePath();

  const geoL = new ExtrudeGeometry(shape, {
    depth: thickness,
    bevelEnabled: true,
    bevelSegments: 1,
    bevelSize: 0.01,
    bevelThickness: 0.01,
    bevelOffset: 0,
  });
  geoL.center();
  return geoL;
}

export function initTank(
  camera: PerspectiveCamera,
  renderer: WebGLRenderer,
  scene: Scene,
  controls: OrbitControls
) {
  let gui: GUI = new GUI({});

  const textureLoader = new TextureLoader();

  const { PI, sin, cos, atan2 } = Math;

  camera.position.set(6, 8, 4);
  controls.screenSpacePanning = false;
  controls.mouseButtons = {
    LEFT: MOUSE.PAN,
    MIDDLE: MOUSE.DOLLY,
    RIGHT: MOUSE.ROTATE,
  };

  controls.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE };
  controls.update();

  const axis = new AxesHelper(10);
  scene.add(axis);

  renderer.setClearColor(0xaaaaaa);
  renderer.shadowMap.enabled = true;
  const tank = new Group();

  let params = {
    camera: "默认",
    automove: false, // 随轨迹移动
    needShot: false,
    shot() {
      params.needShot = true;
    },
    export(){
      exportGLB(tank,'tank')
    }
  };
  /**
   * 地面  坦克
   */

  const plane = new PlaneGeometry(50, 50); // 宽高，和宽高分几段，分段越多，顶点越多

  const floorMat = new MeshPhysicalMaterial({
    color: 0xcc8866,
  });
  const floor = new Mesh(plane, floorMat);

  floor.receiveShadow = true;
  floor.rotateX(-PI / 2);

  /**
   * 灯光
   */
  const light1 = new DirectionalLight(0xffffff, 1);
  light1.position.set(10, 20, 10);
  light1.castShadow = true;
  light1.shadow.mapSize.set(2048, 2048);

  const d = 50;
  light1.shadow.camera.left = -d;
  light1.shadow.camera.right = d;
  light1.shadow.camera.bottom = -d;
  light1.shadow.camera.top = d;
  light1.shadow.camera.near = 1;
  light1.shadow.camera.far = d;

  const light2 = new DirectionalLight(0xffffff, 0.1);
  light2.position.set(-10, -20, -10);

  scene.add(light1, light2, floor);

  // tank is a whole
  tank.name = "坦克";
  const tankSize = {
    wheelRadius: 0.48,
    wheelThickness: 0.3,
    wheelSegments: 12,
    carRadius: 1,
    speed: 0.05,
  };

  const bodyMat = new MeshStandardMaterial({
    color: 0x6688aa,
    metalness: 1,
    side: DoubleSide,
  });
  const body = new Mesh(new SphereGeometry(1), bodyMat);
  body.castShadow = true;

  /**
   * 轮子
   */
  const wheelGeo = new CylinderGeometry(
    tankSize.wheelRadius,
    tankSize.wheelRadius,
    tankSize.wheelThickness,
    tankSize.wheelSegments
  );

  const wheelMat = new MeshStandardMaterial({ color: 0x888888, metalness: 1 });
  const cx = tankSize.carRadius + tankSize.wheelThickness / 2;

  const wheels = [-cx, cx].map((x) => {
    const mesh = new Mesh(wheelGeo, wheelMat);
    mesh.rotateZ(PI / 2);
    mesh.castShadow = true;
    mesh.position.set(x, tankSize.wheelRadius - tankSize.carRadius + 0.08, 0);
    tank.add(mesh);
    return mesh;
  });

  /**
   * 履带
   */
  const caterpillarParams = {
    unitX: 0.5,
    unitY: 0.2,
    unitOffset: 0.1,
    lenX: 1,
    height: 0.7,
    width: 0.2,
    thickness: 0.08,
    count: 25,
  };
  const caterpillar = new Group();
  const matL = new MeshStandardMaterial({
    metalness: 1,
    color: 0xeeffee,
    roughness: 0.6,
  });

  const geoPart = generatePart(
    caterpillarParams.unitX * 0.5,
    caterpillarParams.unitY * 0.5,
    caterpillarParams.unitOffset,
    caterpillarParams.thickness,
    0.5
  );

  const part1 = new Mesh(geoPart, matL);
  part1.visible = false;

  const caterpillarCurve = new CatmullRomCurve3(
    [
      new Vector3(0, 0.1, caterpillarParams.lenX - 0.3),
      new Vector3(0, 0, 0),
      new Vector3(0, 0.1, 0.3 - caterpillarParams.lenX),
      new Vector3(0, caterpillarParams.height, -caterpillarParams.lenX),
      new Vector3(0, caterpillarParams.height, caterpillarParams.lenX),
    ],
    true,
    "chordal"
  );
  const pointsCaterpillar = caterpillarCurve
    .getPoints(100)
    .map((v) => new Vector2(v.z, v.y));

  const shapeGeo = new ExtrudeGeometry(new Shape(pointsCaterpillar), {
    bevelEnabled: true,
    bevelThickness: 0.01,
    bevelSize: 0.01,
    bevelSegments: 1,
    curveSegments: 100,
    depth: 0.2,
  });
  shapeGeo.translate(0, 0, -0.1);
  shapeGeo.rotateY(PI / 2);

  const caterpillarSideL = new Mesh(shapeGeo, matL);
  const caterpillarSideR = new Mesh(shapeGeo, matL);
  const caterpillarL = instanceAlongCurve(
    caterpillarCurve,
    caterpillarParams.count,
    part1,
    "y"
  );
  const caterpillarR = instanceAlongCurve(
    caterpillarCurve,
    caterpillarParams.count,
    part1,
    "y"
  );
  console.log(caterpillarCurve, "caterpillarCurve");

  caterpillarL.name = "履带L";
  caterpillarR.name = "履带R";
  caterpillarL.castShadow = true;
  caterpillarR.castShadow = true;
  const lx = tankSize.carRadius + caterpillarParams.width * 0.5;
  caterpillarL.position.set(
    lx,
    caterpillarParams.thickness * 0.5 - tankSize.carRadius,
    0
  );
  caterpillarR.position.set(
    -lx,
    caterpillarParams.thickness * 0.5 - tankSize.carRadius,
    0
  );
  caterpillarSideL.position.copy(caterpillarL.position);
  caterpillarSideR.position.copy(caterpillarR.position);
  caterpillar.add(
    caterpillarL,
    caterpillarR,
    caterpillarSideL,
    caterpillarSideR
  );
  tank.add(caterpillar);
  let offsetL = 0,
    offsetR = 0;
  /**
   * 若要准确更新，加个距离参数吧
   * @param offset
   */
  function movePartsAlongPath(
    path: Curve<Vector3>,
    axis = "x",
    offset = 0,
    parts: Group,
    distance = false
  ) {
    let offset1 = offset;
    if (distance) {
      offset1 = offset / path.getLength();
    }
    const matrixArr = getInstancesMatrixAlongCurve(
      path,
      caterpillarParams.count,
      axis,
      offset1
    );
    const dataView = new Float32Array(matrixArr);
    parts.children.forEach((item: InstancedMesh, index) => {
      item.instanceMatrix.array = dataView;
      item.instanceMatrix.needsUpdate = true;
    });
  }

  // 炮管需要有一个跟随的相机的 所以
  const barrel = new Group();
  barrel.name = "炮管";
  const tube = new CylinderGeometry(0.15, 0.15, 2.5, 6, 1, true);
  tube.rotateX(PI / 2);
  tube.translate(0, 0, 1.25);
  const tubeMesh = new Mesh(tube, bodyMat);

  tubeMesh.castShadow = true;

  const barrelCamera = makeCamera(new Vector3(0, 0, 1.5));

  barrel.add(tubeMesh, barrelCamera);

  barrel.position.set(0, tankSize.carRadius * 0.7, 0);

  const tankCamera = makeCamera(
    body.position.clone().add(new Vector3(0, 1, -4))
  );

  tank.add(body, barrel, tankCamera);
  tank.position.set(0, 1, 0);
  barrel.rotation.order = "YXZ";
  // 这个就是线速度 默认0 还是加个方向  前后控制正负,0就是静止  左右控制旋转
  let tankNormal = new Vector3(0, 0, 0);
  let angleVelocity = 0;
  let wOrS = 0;

  const targetObj = new Group();
  let targetMesh = new Mesh(
    new SphereGeometry(0.5, 6, 3),
    new MeshPhongMaterial({ color: 0x00ff00, flatShading: true })
  );
  targetMesh.castShadow = true;
  targetObj.add(targetMesh);

  targetObj.position.set(0, 4, 2);

  /**
   * 辅助显示
   * 零件
   * 曲线
   *
   */
  const caterpillarLine = new CurveLine(caterpillarCurve);
  caterpillarLine.visible = false;

  scene.add(tank, targetObj, part1, caterpillarLine);

  function makeCamera(pos: Vector3 = new Vector3(0, 10, -10)) {
    const c = new PerspectiveCamera(40, camera.aspect, 0.1, 100);
    c.lookAt(new Vector3(0, 0, 1));
    c.position.copy(pos);
    return c;
  }

  let diammonds = {
    ArrowUp: [barrel.rotation.x, -0.1],
    ArrowDown: [barrel.rotation.x, 0.1],
    ArrowLeft: [barrel.rotation.y, -0.1],
    ArrowRight: [barrel.rotation.y, 0.1],
  };

  /**
   *
   * 轨迹
   */
  const curve = new SplineCurve([
    new Vector2(-6, -2),
    new Vector2(-6, -8),
    new Vector2(8, 0),
    new Vector2(3, 8),
    new Vector2(-6, 4),
    new Vector2(-6, -2),
  ]);

  const splineObject = new CurveLine(curve, 0xff00ff);
  splineObject.position.y = 0.05;
  splineObject.rotation.x = 0.5 * PI;

  scene.add(splineObject);
  // let linearVelocity = new Vector2()

  /**
   * 虽然键盘事件keydown 的持续触发只会有一个键， 但是按住多个键，每个键至少会触发一次。  所以这里还是直接合成速度，松开时清空速度，就可以
   */

  function dirControl(e: KeyboardEvent) {
    if (params.automove) return;
    switch (e.key) {
      case "ArrowUp":
        barrel.rotation.x -= 0.1;
        if (barrel.rotation.x < -PI / 2) {
          barrel.rotation.x = -PI / 2;
        }

        break;
      case "ArrowDown":
        barrel.rotation.x += 0.1;
        if (barrel.rotation.x > 0.3) {
          barrel.rotation.x = 0.3;
        }

        break;
      case "ArrowRight":
        barrel.rotation.y -= 0.1;

        break;
      case "ArrowLeft":
        barrel.rotation.y += 0.1;

        break;

      case "w":
        wOrS = 1;
        // 前进1 周长2PI *r  也许是速度太快了 看不出来轮子在转

        break;
      case "a":
        tank.rotation.y += 0.1;
        movePartsAlongPath(
          caterpillarCurve,
          "y",
          -0.1 * tank.rotation.y,
          caterpillarL,
          false
        );
        movePartsAlongPath(
          caterpillarCurve,
          "y",
          0.1 * tank.rotation.y,
          caterpillarR,
          false
        );
        break;
      case "s":
        wOrS = -1;

        break;
      case "d":
        tank.rotation.y -= 0.1;
        movePartsAlongPath(
          caterpillarCurve,
          "y",
          0.1 * tank.rotation.y,
          caterpillarL,
          false
        );
        movePartsAlongPath(
          caterpillarCurve,
          "y",
          -0.1 * tank.rotation.y,
          caterpillarR,
          false
        );
        // 由于起始于z 需要 -90
        // tankNormal.set(cos(tank.rotation.y),0, -sin(tank.rotation.y))
        break;
    }
  }

  function stopMove(e: KeyboardEvent) {
    let { key } = e;
    if (key === "w" || key === "s") {
      wOrS = 0;
    }
    if (key === "a" || key === "d") {
      angleVelocity = 0;
    }
  }

  console.log(scene);

  let reqid,
    id,
    step = Math.PI / 180;
  let tankPosition = new Vector2(),
    tankTarget = new Vector2();

  /**
   * 按轨迹移动
   * @param t {number}
   */
  function moveTrack(t) {
    //插值需要在0 1之间
    let tankTime = (t / 50000) % 1;
    curve.getPoint(tankTime, tankPosition);
    curve.getPoint((tankTime + 0.01) % 1, tankTarget); // 下一个位置，就是当前位置的朝向

    // 移动时 坦克的方向是沿着轨迹的， 炮筒的方向却是指向靶子的
    tank.position.set(tankPosition.x, 1, tankPosition.y); // 这里不必顾忌y轴旋转的问题，因为原先是xyo平面得来的坐标，然后整个平面旋转了
    tank.rotation.y = atan2(
      tankTarget.x - tankPosition.x,
      tankTarget.y - tankPosition.y
    );
    // tank.lookAt(tankTarget.x, 1,tankTarget.y) ; 结果会有非Y轴的旋转

    // 对于自身来说一直往前 轮子就是那个方向
    // wheels.forEach((item)=> item.rotation.y = -tankTime )
    wheels.forEach((item) => item.rotateY(-0.1)); // 没想到 rotateX()方法和直接修改 rotation差别挺大的
    // 我当时想的是第一次旋转把 y轴歪下来了，但是第一次旋转是绕z ，不会影响到才对， 看来这就是两种欧拉旋转了
    let dist = 0;
    curve.getLength();
    // @ts-ignore
    let lens = curve.cacheArcLengths,
      count = lens.length - 1;
    for (let i = 0; i <= count; i++) {
      if (tankTime <= i / count) {
        let factor = (i / count - tankTime) * count;
        console.log(factor);

        dist = lens[i] * factor + lens[i - 1] * (1 - factor);
        break;
      }
    }

    movePartsAlongPath(caterpillarCurve, "y", -dist, caterpillarL, true);
    movePartsAlongPath(caterpillarCurve, "y", -dist, caterpillarR, true);
    barrel.lookAt(targetObj.position);
  }
  let dist = 0;

  function ani(t = 0) {
    targetObj.position.y = sin((2 * t) / 1000) * 2 + 4;

    if (params.automove) {
      moveTrack(t);
    } else {
      // 更新速度的方向 和 角度
      tankNormal.set(
        sin(tank.rotation.y) * tankSize.speed,
        0,
        cos(tank.rotation.y) * tankSize.speed
      );
      tankNormal.multiplyScalar(wOrS);
      tank.position.add(tankNormal);
      // 有了方向轮子也可以直接更新了。
      wheels.forEach((item) =>
        item.rotateY((-wOrS * tankSize.speed) / tankSize.wheelRadius)
      );
      dist += tankSize.speed * wOrS;
      if (wOrS) {
        let len = caterpillarCurve.getLength();
        offsetL = dist / len;

        movePartsAlongPath(caterpillarCurve, "y", offsetL, caterpillarL, false);
        movePartsAlongPath(caterpillarCurve, "y", offsetL, caterpillarR, false);
      }
    }
    // movePartsAlongPath(-t *0.0001);
    // cameraHelper.update()
    renderer.render(scene, cameraMap[params.camera]);

    if (params.needShot) {
      canvasToImage(renderer.domElement, "tank");
      params.needShot = false;
    }
    requestAnimationFrame(ani);
  }

  const cameraMap = {
    默认: camera,
    坦克: tankCamera,
    炮筒: barrelCamera,
  };

  function initGui(container: HTMLElement) {
    container.append(gui.domElement);

    gui
      .add(params, "camera", ["默认", "坦克", "炮筒"])
      .setValue("默认")
      .onChange((val: string) => {
        if (val === "默认") {
          controls.enabled = true;
        } else {
          controls.enabled = false;
        }
        //  cameraHelper.camera = cameraMap[val] ;
      });
    gui.add(params, "automove").name("自瞄");
    gui.addFolder(tip).close();
    const folder1 = gui.addFolder("部件");
    folder1.close();
    folder1.add(splineObject, "visible").name("显示路径");
    folder1.add(axis, "visible").name("显示坐标轴");
    folder1.add(tank, "visible").name("显示坦克");
    folder1.add(targetObj, "visible").name("显示靶子");
    folder1.add(caterpillarLine, "visible").name("显示曲线");
    folder1.add(part1, "visible").name("显示零件");
    folder1.add(caterpillar, "visible").name("显示履带");
    folder1.add(barrel, "visible").name("显示炮管");
    folder1.add(body, "visible").name("显示身子");

    gui.add(params, "shot").name("截图");
    gui.add(params,'export' )
  }

  let tip = "WS 前进后退, AD 左右, \n 方向键控制炮筒 ";

  function notify() {
    if (Notification.permission === "granted") {
      new Notification("提示", {
        body: tip,
      });
    } else if (Notification.permission !== "denied") {
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          new Notification("提示", {
            body: tip,
          });
        }
      });
    }
  }

  window.addEventListener("keydown", dirControl);
  window.addEventListener("keyup", stopMove);
  initGui(document.body);
  ani();
}
